package game;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Board {
	private List<Piece> pieces = new ArrayList<Piece>();
	private Point exit;
	private int width;
	private int height;
	private PieceBlue blue;
	private int[][] tiles;

	/*
	 * Board 0 - no piece -1 - blue piece >0 - other pieces each piece has a
	 * different number
	 */

	public Board(int width, int height, Point exit, PieceBlue blue,
			List<Piece> pieces) {
		this.exit = exit;
		this.width = width;
		this.height = height;
		this.pieces = pieces;
		if (!validPieces(pieces)) {
			throw new InvalidPiecesException();
		}
		this.blue = blue;
		tiles = new int[width][height];
		setFirstTiles();
		setBlueTile();
	}

	public List<Piece> getPieces() {
		return pieces;
	}

	public int[][] getTiles() {
		return tiles;
	}

	private boolean validPieces(List<Piece> pieces) {
		for (Piece p : pieces) {
			if (!isValidPiece(p)) {
				return false;
			}
		}
		return true;
	}

	private boolean isValidPiece(Piece p) {
		if (!isValidPoint(p.getPosition())) {
			return false;
		}
		if (p.isVertical()) {
			Point pos = p.getPosition();
			if (!isValidPoint(new Point(pos.x, pos.y + p.getSize() - 1))) {
				return false;
			}
		} else {
			Point pos = p.getPosition();
			if (!isValidPoint(new Point(pos.x + p.getSize() - 1, pos.y))) {
				return false;
			}
		}
		return true;
	}

	private void setBlueTile() {
		if (blue.getPosition().equals(exit)) {
			tiles[blue.getPosition().x][blue.getPosition().y] = blue.getId();
		} else {
			if (blue.isVertical()) {
				for (int i = 0; i < blue.getSize(); i++) {
					tiles[blue.getPosition().x][blue.getPosition().y + i] = blue
							.getId();
				}
			} else {
				for (int i = 0; i < blue.getSize(); i++) {
					if (blue.getPosition().x + i < width)
						tiles[blue.getPosition().x + i][blue.getPosition().y] = blue
								.getId();
				}
			}
		}
	}

	private void setFirstTiles() {
		for (Piece piece : pieces) {
			if (piece.isVertical()) {
				for (int i = 0; i < piece.getSize(); i++) {
					tiles[piece.getPosition().x][piece.getPosition().y + i] = piece
							.getId();
				}
			} else {
				for (int i = 0; i < piece.getSize(); i++) {
					tiles[piece.getPosition().x + i][piece.getPosition().y] = piece
							.getId();
				}
			}
		}
	}

	public boolean isGoal() {
		return (blue.getPosition().y == exit.y && blue.getPosition().x == exit.x);
	}

	public Board movePiece(Piece piece, Point pointToMove) {
		if (!pointToMove.equals(piece.getPosition())
				&& isValidMove(piece, pointToMove)) {

			Board newBoard;
			if (piece instanceof PieceBlue) {
				PieceBlue newBluePiece = new PieceBlue(piece.getId(),
						pointToMove, piece.getSize(), piece.isVertical());
				newBoard = new Board(width, height, exit, newBluePiece,
						this.pieces);
			} else {
				List<Piece> newPieces = new ArrayList<Piece>();
				newPieces.addAll(this.pieces);
				newPieces.remove(piece);
				Piece newPiece = new Piece(piece.getId(), pointToMove, piece
						.getSize(), piece.isVertical());
				newPieces.add(newPiece);
				newBoard = new Board(width, height, exit, blue, newPieces);
			}

			// Update tiles of new board
			newBoard.updateTiles(piece, pointToMove);

			return newBoard;
		}
		return null;
	}

	private void updateTiles(Piece piece, Point pointToMove) {
		Point initial = piece.getPosition();

		if (piece.isVertical()) {

			if (pointToMove.y > initial.y) {// MOVE DOWN
				for (int i = initial.y; i < pointToMove.y; i++) {
					tiles[initial.x][i] = 0;
				}
				for (int i = pointToMove.y; i < pointToMove.y + piece.getSize(); i++) {
					tiles[initial.x][i] = piece.getId();
				}
			} else {// MOVE UP
				for (int i = initial.y; i < initial.y + piece.getSize(); i++) {
					tiles[initial.x][i] = 0;
				}
				for (int i = pointToMove.y; i < pointToMove.y + piece.getSize(); i++) {
					tiles[initial.x][i] = piece.getId();
				}
			}
		} else {

			if (pointToMove.x > initial.x) {// MOVE RIGHT
				for (int i = initial.x; i < initial.x + piece.getSize(); i++) {
					tiles[i][initial.y] = 0;
				}
				for (int i = pointToMove.x; i < pointToMove.x + piece.getSize(); i++) {
					if (i < width) {
						tiles[i][initial.y] = piece.getId();
					}
				}
			} else { // MOVE LEFT
				for (int i = pointToMove.x; i < initial.x + piece.getSize(); i++) {
					tiles[i][initial.y] = 0;
				}
				for (int i = pointToMove.x; i < piece.getSize() + pointToMove.x; i++) {
					tiles[i][initial.y] = piece.getId();
				}
			}
		}
	}

	private boolean isValidMove(Piece piece, Point pointToMove) {

		if (isValidPoint(pointToMove)) {

			Point initial = piece.getPosition();
			Point end = pointToMove;

			if (piece.isVertical()) {
				if (initial.x != end.x) {
					return false;
				}
				// MOVE DOWN
				if (pointToMove.y > initial.y) {
					if (end.y + piece.getSize() - 1 < height) {
						int i = initial.y;
						boolean isValid = true;
						while (i < end.y + piece.getSize() && isValid) {
							if (tiles[initial.x][i] != 0
									&& tiles[initial.x][i] != piece.getId()) {
								isValid = false;
							}
							i++;
						}
						return isValid;

					}
				}
				// MOVE UP
				else {
					if (end.y >= 0) {
						int i = initial.y + piece.getSize() - 1; // 3
						boolean isValid = true;
						// end.y + piece.getSize() 5
						int j = end.y;
						while (j < i && isValid) {
							if (tiles[initial.x][j] != 0
									&& tiles[initial.x][j] != piece.getId()) {
								isValid = false;
							}
							j++;
						}
						return isValid;
					}
				}
			} else {
				if (initial.y != end.y) {
					return false;
				}
				// MOVE RIGHT
				if (pointToMove.x > initial.x) {
					if (end.x + piece.getSize() - 1 < width) {
						int i = initial.x + piece.getSize();
						boolean isValid = true;
						while (i < end.x + piece.getSize() && isValid) {
							if (tiles[i][initial.y] != 0) {
								isValid = false;
							}
							i++;
						}
						return isValid;
					}
					// Piece Blue could be outside the board. In this case, the
					// player wins.
					else {
						if (piece instanceof PieceBlue && end.x == width - 1) {
							int i = initial.x + piece.getSize();
							boolean isValid = true;
							while (i < width && isValid) {
								if (tiles[i][initial.y] != 0) {
									isValid = false;
								}
								i++;
							}
							return isValid;
						}
					}
				} else { // MOVE LEFT
					if (end.x >= 0) {
						int i = end.x;
						boolean isValid = true;
						while (i < initial.x + piece.getSize() && isValid) {
							if (tiles[i][initial.y] != 0
									&& piece.getId() != tiles[i][initial.y]) {
								isValid = false;
							}
							i++;
						}
						return isValid;
					}
				}
			}
		}
		return false;
	}

	private boolean isValidPoint(Point point) {
		boolean answer = false;

		if ((point.x < width && point.x >= 0)
				&& (point.y < height && point.y >= 0)) {
			answer = true;
		}

		return answer;
	}

	private boolean isCompleteInBoard(Piece piece) {
		int cant = 0;
		for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				if (tiles[i][j] == piece.getId())
					cant++;
			}
		}
		return piece.getSize() == cant;
	}

	private boolean checkState() {
		for (Piece piece : pieces) {
			if (!isCompleteInBoard(piece))
				return false;
		}
		return true;
	}

	public String toString() {

		String board = new String();
		for (int i = 0; i < height; i++) {
			for (int j = 0; j < width; j++) {
				board += "  |  " + tiles[j][i] + "  |";
			}
			board += "\n";
		}
		if (!checkState()) {
			return board + '\n' + "EL TABLERO ESTA MAL FORMADO";
		} else {
			return board;
		}
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (Board.class != obj.getClass())
			return false;
		else {
			for (int i = 0; i < this.width; i++) {
				for (int j = 0; j < this.height; j++) {
					if (this.tiles[i][j] != ((Board) obj).tiles[i][j]) {
						return false;
					}
				}
			}
			return true;
		}
	}

	public Piece getBlue() {
		return blue;
	}

	@Override
	public int hashCode() {
		int result = 0;
		for (Piece p : pieces) {
			result += p.hashCode();
		}
		result += blue.hashCode();
		return result;
	}

}
